package com.michael.retail.auth.config;

import cn.hutool.json.JSONUtil;
import com.michael.retail.auth.entity.OAuth2UserDetails;
import com.michael.retail.auth.granter.PasswordValidCodeGranter;
import com.michael.retail.auth.service.ValidCodeService;
import com.michael.retail.commons.pojo.global.R;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter;
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.RandomValueAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter;
import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter;
import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory;
import org.springframework.security.oauth2.provider.token.*;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import org.springframework.security.web.AuthenticationEntryPoint;

import java.io.PrintWriter;
import java.security.KeyPair;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 类功能描述:
 * <pre>
 *   oauth2 认证服务器配置
 * </pre>
 *
 * @author Michael
 * @version 1.0
 * @date 2021/7/5 16:35
 */
@SuppressWarnings("AlibabaClassNamingShouldBeCamel")
@Slf4j
@Configuration
@EnableAuthorizationServer
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class OAuth2ServerConfig extends AuthorizationServerConfigurerAdapter {

    private final AuthenticationManager authenticationManager;
    private final UserDetailsService oAuth2UserDetailsService;
    private final ClientDetailsService oauth2ClientDetailsService;

    private final ValidCodeService validCodeService;

    @Autowired
    private RandomValueAuthorizationCodeServices authorizationCodeServices;

    private final BCryptPasswordEncoder passwordEncoder;


    /**
     * 配置认证客户端
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 应用多，可以使用客户端管理
        clients.withClientDetails(oauth2ClientDetailsService);

        // 应用少，直接放入内存中
        //clients.inMemory()
        //       .withClient("client-app")
        //       .secret(passwordEncoder.encode("123456"))
        //       .scopes("all")
        //       .authorizedGrantTypes("password", "refresh_token")
        //       .accessTokenValiditySeconds(1 * 60 * 60)
        //       .refreshTokenValiditySeconds(24 * 60 * 60);
    }

    /**
     * 自定义授权服务配置
     * 使用密码模式需要配置
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                // 默认除密码模式外，所有授权模式均支持，密码模式需要显示注入authenticationManager开启
                .authenticationManager(authenticationManager)
                // 自定义用户密码加载服务
                .userDetailsService(oAuth2UserDetailsService)
                .accessTokenConverter(jwtAccessTokenConverter())
                // 设了 tokenGranter 后该配制失效,需要在下面方法 tokenServices() 中设置
                //.tokenEnhancer(tokenEnhancerChain)
                .tokenGranter(tokenGranter())
                // 允许访问的方法
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
                // refresh token有两种使用方式：重复使用(true)、非重复使用(false)，默认为true
                //      1 重复使用：access token过期刷新时， refresh token过期时间未改变，仍以初次生成的时间为准
                //      2 非重复使用：access token过期刷新时， refresh token过期时间延续，在refresh token有效期内刷新便永不失效达到无需再次登录的目的
                .reuseRefreshTokens(true)
        ;
    }

    /**
     * 自定义授权令牌端点的安全约束
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {

        ClientEndpointFilter endpointFilter = new ClientEndpointFilter(security);
        endpointFilter.setAuthenticationEntryPoint(authenticationEntryPoint());
        endpointFilter.afterPropertiesSet();

        // 参考：@url https://blog.csdn.net/dechengtju/article/details/107775360
        // 客户端认证之前的过滤器
        security.addTokenEndpointAuthenticationFilter(endpointFilter);
    }

    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {

        // 配置JWT的内容增强器
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> delegates = new ArrayList<>();
        delegates.add(tokenEnhancer());
        delegates.add(jwtAccessTokenConverter());
        tokenEnhancerChain.setTokenEnhancers(delegates);

        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(jwtTokenStore());
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setAccessTokenValiditySeconds(2 * 60 * 60);
        tokenServices.setRefreshTokenValiditySeconds(10 * 60 * 60);

        // 如果没有设置它,JWT就失效了.
        tokenServices.setTokenEnhancer(tokenEnhancerChain);
        return tokenServices;
    }

    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint() {
        return (request, response, e) -> {
            response.setStatus(HttpStatus.BAD_REQUEST.value());
            response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
            response.setHeader(HttpHeaders.CACHE_CONTROL, "no-cache");
            String body = JSONUtil.toJsonStr(R.failed("client 参数错误"));
            // 在 getWriter 前设置编码，否则乱码
            response.setCharacterEncoding("UTF-8");
            PrintWriter writer = response.getWriter();
            writer.print(body);
            writer.flush();
        };
    }


    /**
     * 自定义授权模式
     */
    @Bean
    public TokenGranter tokenGranter() {
        return new CompositeTokenGranter(getAllTokenGranters());
    }

    /**
     * JWT内容增强
     */
    @Bean
    public TokenEnhancer tokenEnhancer() {
        return (accessToken, authentication) -> {

            Map<String, Object> addMap = new HashMap<>();

            OAuth2UserDetails userDetails = (OAuth2UserDetails) authentication.getPrincipal();

            // 增强部分
            addMap.put("userId", userDetails.getId());
            addMap.put("username", userDetails.getUsername());

            DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken;
            token.setAdditionalInformation(addMap);
            return accessToken;
        };
    }

    @Bean
    public TokenStore jwtTokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setKeyPair(getKeyPair());
        return converter;
    }

    @Bean
    public KeyPair getKeyPair() {
        //从classpath下的证书中获取秘钥对
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(
                new ClassPathResource("jwt.jks"), "123456".toCharArray()
        );
        return keyStoreKeyFactory.getKeyPair("jwt", "123456".toCharArray());
    }

    /**
     * 所有授权模式：默认的5种模式 + 自定义的模式
     */
    private List<TokenGranter> getAllTokenGranters() {

        AuthorizationServerTokenServices tokenServices = tokenServices();
        AuthorizationCodeServices authorizationCodeServices = authorizationCodeServices();
        OAuth2RequestFactory requestFactory = requestFactory();

        //获取默认的授权模式
        List<TokenGranter> tokenGranters = getDefaultTokenGranters(
                tokenServices, authorizationCodeServices, requestFactory
        );

        if (authenticationManager != null) {
            // 添加密码加图形验证码模式
            tokenGranters
                    .add(new PasswordValidCodeGranter(authenticationManager, tokenServices, oauth2ClientDetailsService,
                            requestFactory, validCodeService
                    ));

            //// 添加openId模式
            //tokenGranters.add(new OpenIdGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));
            //// 添加手机号加密码授权模式
            //tokenGranters.add(new MobilePwdGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));
        }
        return tokenGranters;
    }


    /**
     * 默认的授权模式
     */
    private List<TokenGranter> getDefaultTokenGranters(
            AuthorizationServerTokenServices tokenServices,
            AuthorizationCodeServices authorizationCodeServices,
            OAuth2RequestFactory requestFactory
    ) {
        List<TokenGranter> tokenGranters = new ArrayList<>();
        // 添加授权码模式
        tokenGranters.add(
                new AuthorizationCodeTokenGranter(
                        tokenServices, authorizationCodeServices, oauth2ClientDetailsService, requestFactory
                )
        );
        // 添加刷新令牌的模式
        tokenGranters.add(new RefreshTokenGranter(tokenServices, oauth2ClientDetailsService, requestFactory));
        // 添加隐士授权模式
        tokenGranters.add(new ImplicitTokenGranter(tokenServices, oauth2ClientDetailsService, requestFactory));
        // 添加客户端模式
        tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, oauth2ClientDetailsService, requestFactory));
        if (authenticationManager != null) {
            // 添加密码模式
            tokenGranters.add(
                    new ResourceOwnerPasswordTokenGranter(
                            authenticationManager, tokenServices, oauth2ClientDetailsService, requestFactory
                    )
            );
        }
        return tokenGranters;
    }

    private AuthorizationCodeServices authorizationCodeServices() {
        if (authorizationCodeServices == null) {
            authorizationCodeServices = new InMemoryAuthorizationCodeServices();
        }
        return authorizationCodeServices;
    }

    private OAuth2RequestFactory requestFactory() {
        return new DefaultOAuth2RequestFactory(oauth2ClientDetailsService);
    }
}
